﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Codeflows.Results;

namespace Codeflows
{
    class ArgumentParser<TC> : ExpressionVisitor where TC : Codeflow
    {
        Func<Expression, bool> _validator;
        string _actionName;
        readonly Dictionary<string, object> _arguments = new Dictionary<string, object>();
        string _currentArgument;

        public ParsedExpression Parse(Expression<Func<TC, Result>> expression)
        {
            _validator = e => e is LambdaExpression;
            Visit(expression);
            return new ParsedExpression(_actionName, _arguments);
        }

        public ParsedExpression Parse(Expression<Func<TC, Task<Result>>> expression)
        {
            _validator = e => e is LambdaExpression;
            Visit(expression);
            return new ParsedExpression(_actionName, _arguments);
        }

        public override Expression Visit(Expression node)
        {
            if (!_validator(node))
                throw new NotSupportedException("Not supported.");

            return base.Visit(node);
        }

        protected override Expression  VisitLambda<T>(Expression<T> node)
        {
            _validator = e => e is MethodCallExpression;            
            return base.VisitLambda(node);
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            _actionName = node.Method.Name;
            _validator = e => e is ConstantExpression || e is MemberExpression || e is ParameterExpression;
            var parameters = node.Method.GetParameters();

            for (var i = 0; i < parameters.Length; i++)
            {
                var current = parameters[i];
                if (current.ParameterType == typeof(IActionContext))
                    continue;

                _currentArgument = current.Name;                
                _arguments[_currentArgument] = null;
                Visit(node.Arguments[i]);
            }

            return node;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            _arguments[_currentArgument] = node.Value;
            return node;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            var value = ExtractValueFromConstantExpression(node);

            if (value == null)
                throw new NotSupportedException("Only constant expressions are supported.");

            _arguments[_currentArgument] = value;

            return node;
        }

        static object ExtractValueFromConstantExpression(Expression node)
        {
            var constantExpression = node as ConstantExpression;
            if (constantExpression != null)
                return constantExpression.Value;

            var memberExpression = node as MemberExpression;
            if (memberExpression == null)
                return null;

            var parent = ExtractValueFromConstantExpression(memberExpression.Expression);

            if (parent == null)
                return null;


            if (memberExpression.Member.MemberType == MemberTypes.Field)
            {
                var field = parent.GetType().GetField(memberExpression.Member.Name,
                                                      BindingFlags.Public | BindingFlags.NonPublic |
                                                      BindingFlags.Instance);

// ReSharper disable PossibleNullReferenceException
                return field.GetValue(parent);
// ReSharper restore PossibleNullReferenceException
            }
            
            if (memberExpression.Member.MemberType == MemberTypes.Property)
            {
                var property = parent.GetType().GetProperty(memberExpression.Member.Name,
                                                         BindingFlags.Public | BindingFlags.NonPublic |
                                                         BindingFlags.Instance);

                return property.GetValue(parent, null);
            }

            throw new NotSupportedException();
        }
    }
}